package com.zjj.learn.algorithmPlus.segment1.blockQueue;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 单锁实现
 *
 * @param <E> 元素类型
 * @author zjj_admin
 */
public class BlockingQueue1<E> implements BlockingQueue<E> {
    private final E[] array;
    private int head = 0;
    private int tail = 0;
    /**
     * 元素个数
     */
    private int size = 0;

    @SuppressWarnings("all")
    public BlockingQueue1(int capacity) {
        array = (E[]) new Object[capacity];
    }

    /**
     * 声明一把锁
     */
    ReentrantLock lock = new ReentrantLock();
    /**
     * 声明两个 Condition，一个头部一个尾部
     * 当添加数据时，若队列已经满了就让尾部陷入阻塞状态，只有释放元素之后再将尾部唤醒。
     * 当移除数据时，若队列已经空了就让头部陷入阻塞状态，只有添加元素之后再将头部唤醒。
     */
    Condition tailWaits = lock.newCondition();
    Condition headWaits = lock.newCondition();

    @Override
    public void offer(E e) throws InterruptedException {
        //将锁声明为可打断的
        //和 lock 方法不同，lockInterruptibly 方法在等待获取时，如果遇到线程中断会放弃获取锁
        lock.lockInterruptibly();
        try {
            while (isFull()) {
                tailWaits.await();
            }
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }
            size++;
            //每一次添加数据之后就将头部唤醒
            headWaits.signal();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void offer(E e, long timeout) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            long t = TimeUnit.MILLISECONDS.toNanos(timeout);
            while (isFull()) {
                if (t <= 0) {
                    return;
                }
                t = tailWaits.awaitNanos(t);
            }
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }
            size++;
            //每一次添加数据之后就将头部唤醒
            headWaits.signal();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public E poll() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (isEmpty()) {
                headWaits.await();
            }
            E e = array[head];
            // help GC
            array[head] = null;
            if (++head == array.length) {
                head = 0;
            }
            size--;
            tailWaits.signal();
            return e;
        } finally {
            lock.unlock();
        }
    }

    private boolean isEmpty() {
        return size == 0;
    }

    private boolean isFull() {
        return size == array.length;
    }
}